package com.loong.dilib.core;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.loong.dilib.annotation.DICookie;
import com.loong.dilib.annotation.DIHeader;
import com.loong.dilib.annotation.DIJson;
import com.loong.dilib.annotation.DIParam;
import com.loong.dilib.annotation.DIPath;
import com.loong.dilib.annotation.DIRequest;
import com.loong.dilib.annotation.DIResponse;
import com.loong.dilib.exception.DIConnectException;
import com.loong.dilib.exception.DIFormatException;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

/**
 * Api代理实体处理类
 */
public class ApiInterceptor implements MethodInterceptor {

	private static String chartset = "UTF-8";
	private static CloseableHttpClient httpClient = HttpClients.createDefault();
	private static Gson gson = new Gson();

	private static final String content = "Content-Type";
	private static final String content_default = "application/x-www-form-urlencoded";
	private static final String content_json = "application/json";

	@Override
	public Object intercept(Object object, Method method, Object[] params, MethodProxy methodProxy)
			throws DIConnectException, DIFormatException {

		// REST参数
		Map<String, Object> pathMap = new HashMap<String, Object>();
		// 请求头
		Map<String, Object> headerMap = new LinkedHashMap<String, Object>();
		// Cookie
		StringBuilder cookieStr = new StringBuilder();
		// 请求参数
		StringBuilder paramStr = new StringBuilder();

		// 遍历参数
		Annotation[][] psAs = method.getParameterAnnotations();
		// 参数索引
		int pi = 0;
		for (Annotation[] pAs : psAs) {
			// 请求头注释
			DIHeader headerAnn = null;
			// Cookie注释
			DICookie cookieAnn = null;
			// DIParam
			DIParam paramAnn = null;
			// REST参数注释
			DIPath pathAnn = null;
			// Json注释
			DIJson jsonAnn = null;
			// 遍历注释判断
			for (Annotation pa : pAs)
				if (pa instanceof DIHeader)
					headerAnn = (DIHeader) pa;
				else if (pa instanceof DICookie)
					cookieAnn = (DICookie) pa;
				else if (pa instanceof DIParam)
					paramAnn = (DIParam) pa;
				else if (pa instanceof DIPath)
					pathAnn = (DIPath) pa;
				else if (pa instanceof DIJson)
					jsonAnn = (DIJson) pa;
			// 处理方法参数
			if (headerAnn != null)
				// 请求头
				if (headerAnn.value().isEmpty())
					pushMap(params[pi], headerMap);
				else
					pushMap(headerAnn.value(), params[pi], headerMap);
			else if (cookieAnn != null) {
				// Cookie
				if (cookieAnn.value().isEmpty())
					pushStr(params[pi], ";", cookieStr);
				else
					pushStr(cookieAnn.value(), params[pi], ";", cookieStr);
			} else if (pathAnn != null) {
				// 请求头
				if (pathAnn.value().isEmpty())
					pushMap(params[pi], pathMap);
				else
					pushMap(pathAnn.value(), params[pi], pathMap);
			} else if (jsonAnn != null) {
				// 请求Json参数处理
				jsonParam(params[pi], paramStr);
				// 添加头部信息
				pushMap(content, content_json, headerMap);
			} else {
				// 请求字符串参数处理
				if (paramAnn == null || paramAnn.value().isEmpty())
					pushStr(params[pi], "&", paramStr);
				else
					pushStr(paramAnn.value(), params[pi], "&", paramStr);
				// 添加头部信息
				pushMap(content, content_default, headerMap);
			}
			pi++;
		}

		// 获取方法请求注释
		DIRequest request = method.getAnnotation(DIRequest.class);
		// 获取类请求注释
		DIRequest apiRequest = null;
		for (Class<?> clas : object.getClass().getInterfaces())
			if ((apiRequest = clas.getAnnotation(DIRequest.class)) != null)
				break;

		// 获取请求基础信息
		StringBuilder url = getUrl(apiRequest, request, pathMap);

		// 发送请求
		String html;
		if (request.method() == DIRequest.Method.POST)
			html = post(url, headerMap, cookieStr, paramStr);
		else
			html = get(url, headerMap, cookieStr, paramStr);

		// 处理响应信息
		DIResponse response = method.getAnnotation(DIResponse.class);
		if (response == null)
			// 获取类响应注释
			for (Class<?> clas : object.getClass().getInterfaces())
				if ((response = clas.getAnnotation(DIResponse.class)) != null)
					break;
		if (html == null)
			return null;
		else if (method.getReturnType() == String.class)
			// 按字符串处理
			return html;
		else if (response == null || response.value() == DIResponse.Type.JSON)
			// Jsonp处理
			return fromJson(html, method.getGenericReturnType());
		else if (response.value() == DIResponse.Type.JSONP)
			// Json处理
			return fromJsonp(html, method.getGenericReturnType());
		return null;
	}

	/**
	 * 获取访问地址
	 * 
	 * @param apiRequest api请求注释
	 * @param request 方法请求注释
	 * @param paths REST路径参数集
	 * @return url
	 */
	private StringBuilder getUrl(DIRequest apiRequest, DIRequest request, Map<String, Object> paths) {

		StringBuilder url = new StringBuilder();
		if (apiRequest == null)
			// 没有方法请求注释，直接返回类注释的url
			url.append(request.value());
		else {
			// 类注释访问地址
			String base = apiRequest.value();
			// 方法注释访问地址
			String servlet = request.value();

			// 拼装url
			boolean baseEnd = base.endsWith("/");
			boolean startUrl = servlet.startsWith("/");
			url.append(base);
			if (baseEnd && startUrl)
				url.append(servlet.substring(1));
			else if (!baseEnd && !startUrl)
				url.append("/").append(servlet);
			else
				url.append(servlet);
		}

		// 处理REST参数
		for (Entry<String, Object> path : paths.entrySet()) {
			// 待替换参数标识
			String sign = new StringBuilder().append("{").append(path.getKey()).append("}").toString();
			// 获取替换索引
			int start = url.indexOf(sign);
			if (start == -1)
				continue;
			int end = start + sign.length();
			// 替换参数
			url.replace(start, end, path.getValue() == null ? "" : path.getValue().toString());
		}
		return url;
	}

	/**
	 * GET方式请求
	 * 
	 * @param url 访问地址
	 * @param header 请求头
	 * @param cookie 请求Cookie
	 * @param param 请求参数
	 * @return 响应体
	 * @throws DIConnectException
	 */
	private String get(StringBuilder url, Map<String, Object> header, StringBuilder cookie, StringBuilder param)
			throws DIConnectException {

		String u = url.append("?").append(param).toString();
		HttpGet get = new HttpGet(u);
		// 添加请求头
		for (Entry<String, Object> h : header.entrySet())
			get.addHeader(h.getKey(), h.getValue().toString());
		// 添加Cookie
		if (cookie.length() != 0)
			get.addHeader("Cookie", cookie.toString());
		return getResponse(get);
	}

	/**
	 * POST方式请求
	 * 
	 * @param url 访问地址
	 * @param header 请求头
	 * @param cookie 请求Cookie
	 * @param param 请求参数
	 * @return 响应体
	 * @throws DIConnectException
	 */
	private String post(StringBuilder url, Map<String, Object> header, StringBuilder cookie, StringBuilder param)
			throws DIConnectException {

		HttpPost post = new HttpPost(url.toString());
		StringEntity entity = new StringEntity(param.toString(), chartset);
		// 添加参数
		post.setEntity(entity);
		// 添加请求头
		for (Entry<String, Object> h : header.entrySet())
			post.addHeader(h.getKey(), h.getValue().toString());
		// 添加Cookie
		if (cookie.length() != 0)
			post.addHeader("Cookie", cookie.toString());
		return getResponse(post);
	}

	/**
	 * 请求
	 * 
	 * @param request 请求
	 * @return 响应
	 * @throws IOException
	 */
	private static String getResponse(HttpUriRequest request) throws DIConnectException {

		String httpStr = null;
		CloseableHttpResponse response = null;
		try {
			response = httpClient.execute(request);
			int code = response.getStatusLine().getStatusCode();
			if (code == 200) {
				// 请求成功
				HttpEntity httpEntity = response.getEntity();
				httpStr = EntityUtils.toString(httpEntity, chartset);
			} else
				throw new DIConnectException("Connection failed. error code:" + code);
		} catch (IOException e) {
			throw new DIConnectException("Connection failed", e);
		} finally {
			if (response != null)
				try {
					response.close();
				} catch (IOException e) {
				}
		}
		return httpStr;
	}

	/**
	 * @param bean Bean对象
	 * @param map Map集合
	 */
	private void pushMap(Object bean, Map<String, Object> map) {

		Map<String, Object> hs = convert(bean);
		map.putAll(hs);
	}

	/**
	 * @param name 名称
	 * @param value 值
	 * @param map Map集合
	 */
	private void pushMap(String name, Object value, Map<String, Object> map) {

		if (!map.containsKey(name))
			map.put(name, value);
	}

	/**
	 * @param bean Bean对象
	 * @param split 分隔符
	 * @param str 字符串
	 */
	private void pushStr(Object bean, String split, StringBuilder str) {

		Map<String, Object> cs = convert(bean);
		for (Entry<String, Object> c : cs.entrySet())
			pushStr(c.getKey(), c.getValue(), split, str);
	}

	/**
	 * @param name 名称
	 * @param value 值
	 * @param split 分隔符
	 * @param str 字符串
	 */
	private void pushStr(String name, Object value, String split, StringBuilder str) {

		if (str.length() != 0)
			str.append(split);
		str.append(name).append("=");
		if (value != null)
			str.append(value);
	}

	/**
	 * 添加Json参数
	 * 
	 * @param bean bean对象
	 * @param param 参数串
	 */
	private void jsonParam(Object bean, StringBuilder param) {

		if (param.length() == 0)
			param.append(convertJson(bean));
	}

	/**
	 * 反射转换Bean对象
	 * 
	 * @param bean Bean对象
	 * @return Map集合
	 */
	@SuppressWarnings("unchecked")
	private Map<String, Object> convert(Object bean) {

		if (bean instanceof Map)
			return (Map<String, Object>) bean;
		Map<String, Object> map = new LinkedHashMap<String, Object>();
		if (bean == null)
			return map;
		// 反射获取类
		Class<?> clas = bean.getClass();
		// 遍历父节点
		while (clas != null && !Object.class.equals(clas)) {
			// 获取变量
			Field[] fields = clas.getDeclaredFields();
			for (Field f : fields) {
				f.setAccessible(true);
				Object value = null;
				try {
					// 获取变量值
					value = f.get(bean);
				} catch (IllegalAccessException e) {
					throw new RuntimeException(e);
				}
				// 添加到集合中
				map.put(f.getName(), value == null ? null : value.toString());
			}
			clas = clas.getSuperclass();
		}
		return map;
	}

	/**
	 * 获取Json字符串
	 * 
	 * @param bean Bean对象
	 * @return Json字符串
	 */
	private String convertJson(Object bean) {

		return gson.toJson(bean);
	}

	/**
	 * 获取对象
	 * 
	 * @param json Json字符串
	 * @param type 类型
	 * @return 对象
	 */
	@SuppressWarnings("unchecked")
	private <T> T fromJson(String json, Type type) {

		try {
			return (T) gson.fromJson(json, type);
		} catch (JsonSyntaxException e) {
			throw new DIFormatException("Response body format error: " + json, e);
		}
	}

	/**
	 * 获取对象
	 * 
	 * @param jsonp Jsonp字符串
	 * @param type 类型
	 * @return 对象
	 */
	@SuppressWarnings("unchecked")
	private <T> T fromJsonp(String jsonp, Type type) {

		int start = jsonp.indexOf("{");
		int end = jsonp.lastIndexOf("}") + 1;
		if (start < 0 || start >= end)
			throw new DIFormatException("Response body format error: " + jsonp);
		try {
			return (T) gson.fromJson(jsonp.substring(start, end), type);
		} catch (JsonSyntaxException e) {
			throw new DIFormatException("Response body format error: " + jsonp, e);
		}
	}
}
